package com.sc.byahi.user.server;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.channels.Channels;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;

import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreFailureException;
import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;
import com.google.appengine.api.capabilities.CapabilitiesService;
import com.google.appengine.api.capabilities.CapabilitiesServiceFactory;
import com.google.appengine.api.capabilities.Capability;
import com.google.appengine.api.capabilities.CapabilityStatus;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.files.AppEngineFile;
import com.google.appengine.api.files.FileReadChannel;
import com.google.appengine.api.files.FileService;
import com.google.appengine.api.files.FileServiceFactory;
import com.google.appengine.api.files.FileWriteChannel;
import com.google.appengine.api.images.Image;
import com.google.appengine.api.images.ImagesService;
import com.google.appengine.api.images.ImagesService.OutputEncoding;
import com.google.appengine.api.images.ImagesServiceFactory;
import com.google.appengine.api.images.OutputSettings;
import com.google.appengine.api.images.Transform;
import com.google.appengine.api.urlfetch.HTTPHeader;
import com.google.appengine.api.urlfetch.HTTPMethod;
import com.google.appengine.api.urlfetch.HTTPRequest;
import com.google.appengine.api.urlfetch.HTTPResponse;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;
import com.google.appengine.api.utils.SystemProperty;
import com.sc.byahi.user.shared.Profile;
import com.sc.byahi.user.shared.SharedUtil;

/**
 * Utility for FileSTore and Datastore. For example, storing of Images are used by upload servlet as well as task servlet, hence it is provided as
 * static method.
 */

public class StoreUtil {
	private static final Logger logger = Logger.getLogger("com.sc.byahi.user.server.ServerUtil");
	public static boolean DEV_MODE = (SystemProperty.environment.value() == SystemProperty.Environment.Value.Development);

	private static BlobKey writeBlob(String text) {
		try {
			FileService fileService = FileServiceFactory.getFileService();
			AppEngineFile file = fileService.createNewBlobFile("text/plain");

			FileWriteChannel writeChannel = fileService.openWriteChannel(file, true);
			OutputStream out = Channels.newOutputStream(writeChannel);
			out.write(text.getBytes());
			out.flush();
			writeChannel.closeFinally();
			return fileService.getBlobKey(file);
		} catch (Exception e) {
			logger.log(Level.SEVERE, "Error while wrting text file to Blobstore. ", e);
		}
		return null;
	}

	private static String readBlob(BlobKey blobKey) throws Exception {
		FileService fileService = FileServiceFactory.getFileService();
		AppEngineFile file = fileService.getBlobFile(blobKey);
		FileReadChannel readChannel = fileService.openReadChannel(file, false);
		InputStream reader = Channels.newInputStream(readChannel);
		byte[] bytes = new byte[reader.available()];
		reader.read(bytes);
		readChannel.close();
		return new String(bytes);
	}

	public static List<BlobKey> getUploadedBlobs(HttpServletRequest req) {
		BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
		Map<String, List<BlobKey>> blobs = blobstoreService.getUploads(req);
		List<BlobKey> blobKeys = new ArrayList<BlobKey>();
		for (List<BlobKey> blobKeyList : blobs.values()) {
			blobKeys.addAll(blobKeyList);
		}
		return blobKeys;
	}

	public static void replaceProfileImages(Collection<BlobKey> blobs, DatastoreService datastore, Entity p) {

		if (blobs.size() > 0) {
			// Delete old photos now
			// Null and empty collections are ignored by App Engine
			Object imagesObj = p.getProperty(Profile.IMAGES);
			if (imagesObj != null) {
				BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
				for (String key : (List<String>) imagesObj) {
					BlobKey oldImageBlobKey = new BlobKey(key);
					try {
						logger.log(Level.WARNING, "deleting old image: " + oldImageBlobKey.toString());
						try {
							ImagesService imagesService = ImagesServiceFactory.getImagesService();
							imagesService.deleteServingUrl(oldImageBlobKey);
						} catch (Exception ex) {
							// ignore
						}

						blobstoreService.delete(oldImageBlobKey);
					} catch (BlobstoreFailureException bfe) {
						logger.log(Level.SEVERE, "Error while deleting Blobkey: " + oldImageBlobKey.toString(), bfe);
					}
				}
			}

			List<String> newImages = new ArrayList<String>(4);
			for (BlobKey key : blobs) {
				logger.log(Level.WARNING, "Saving images to profile: " + key.toString());
				newImages.add(key.getKeyString());
			}
			p.setProperty(Profile.IMAGES, newImages);
			p.setProperty(Profile.APPROVED, false);
			datastore.put(p);
		}
	}

	public static List<BlobKey> resizeAndUploadImages(Collection<BlobKey> blobKey, String profileId) {
		List<BlobKey> resizedImagesKeys = new ArrayList<BlobKey>();
		Map<BlobKey, byte[]> imageData = transformImages(blobKey);
		try {
			resizedImagesKeys = uploadResizedImages(profileId, imageData);
			logger.log(Level.INFO, "Resize Image Submit response:" + resizedImagesKeys);
		} catch (Exception e) {
			logger.log(Level.SEVERE, "Exception:", e);
		}
		return resizedImagesKeys;
	}

	private static Map<BlobKey, byte[]> transformImages(Collection<BlobKey> blobKey) {
		CapabilitiesService service = CapabilitiesServiceFactory.getCapabilitiesService();
		CapabilityStatus status = service.getStatus(Capability.IMAGES).getStatus();
		if (status == CapabilityStatus.DISABLED) {
			// Images API is not available.
			return Collections.emptyMap();
		}
		Map<BlobKey, byte[]> imageData = new HashMap<BlobKey, byte[]>();
		ImagesService imagesService = ImagesServiceFactory.getImagesService();
		int height = 500;
		int width = 500;
		for (BlobKey blobKey2 : blobKey) {
			Image oldImage = ImagesServiceFactory.makeImageFromBlob(blobKey2);
			Transform resize = ImagesServiceFactory.makeResize(height, width);
			Image newImage = imagesService.applyTransform(resize, oldImage, new OutputSettings(OutputEncoding.JPEG));
			byte[] imageFile = newImage.getImageData();
			imageData.put(blobKey2, imageFile);
		}
		return imageData;
	}

	private static <T> List<BlobKey> uploadResizedImages(String profileId, Map<T, byte[]> imageData) throws Exception {
		List<BlobKey> resizedImagesKeys = new ArrayList<BlobKey>();
		BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
		String url = blobstoreService.createUploadUrl("/admin/resizeupload");
		url = SharedUtil.fixURL(url);

		URLFetchService urlFetch = URLFetchServiceFactory.getURLFetchService();
		HTTPRequest req = new HTTPRequest(new URL(url), HTTPMethod.POST);

		String boundary = makeBoundary();
		req.setHeader(new HTTPHeader("Content-Type", "multipart/form-data; boundary=" + boundary));

		ByteArrayOutputStream payload = new ByteArrayOutputStream();
		write(payload, "--" + boundary + "\r\n");
		writeParameter(payload, "profileid", profileId + "");
		write(payload, "--" + boundary + "\r\n");
		int counter = 0;
		for (T blobKey2 : imageData.keySet()) {
			counter++;
			byte[] data = imageData.get(blobKey2);
			String name = "photo" + counter;
			writeImage(payload, name, data);
			write(payload, "--" + boundary + "\r\n");
		}

		req.setPayload(payload.toByteArray());

		try {
			HTTPResponse response = urlFetch.fetch(req);
			byte[] content = response.getContent();
			ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(content));
			resizedImagesKeys = (List<BlobKey>) ois.readObject();

		} catch (IOException e) {
			// Need a better way of handling Timeout exceptions here - 10 second deadline
			logger.log(Level.WARNING, "Possible timeout?", e);
		}
		return resizedImagesKeys;

	}

	private static Random random = new Random();

	private static String randomString() {
		return Long.toString(random.nextLong(), 36);
	}

	private static String makeBoundary() {
		return "---------------------------" + randomString() + randomString() + randomString();
	}

	private static void write(OutputStream os, String s) throws IOException {
		os.write(s.getBytes());
	}

	private static void writeParameter(OutputStream os, String name, String value) throws IOException {
		write(os, "Content-Disposition: form-data; name=\"" + name + "\"\r\n\r\n" + value + "\r\n");
	}

	private static void writeImage(OutputStream os, String name, byte[] bs) throws IOException {
		write(os, "Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + name + ".jpg\"\r\n");
		write(os, "Content-Type: image/jpeg\r\n\r\n");
		os.write(bs);
		write(os, "\r\n");
	}
}
